package dhz.storage.controllers;

import dhz.storage.dao.ShareLinkDao;
import dhz.storage.models.ShareLink;
import dhz.storage.models.UploadTask;
import dhz.storage.models.UserUploadTasks;
import dhz.storage.util.FileUtil;
import dhz.storage.util.JwtUtil;
import dhz.storage.util.Response;
import dhz.storage.util.UploadTaskRedisUtil;
import io.swagger.annotations.*;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.server.PathParam;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;

@RestController
@RequestMapping("/interface")
@Api(tags = "上传和下载文件的接口")
public class InterfaceControllers {
    @Resource
    ShareLinkDao link_repo;
    @Autowired
    UploadTaskRedisUtil util;

    static final Logger logger = LoggerFactory.getLogger(InterfaceControllers.class);
    /**
     * 点击链接从网盘上下载文件
     * @param path 文件的路径（相对于用户的根文件夹）
     * @return 一个Response对象，1001意味着下载成功，500意味着下载失败
     * */
    @GetMapping("/download")
    @ApiOperation(value = "下载文件接口", notes = "向这里发送Post请求，之后就会自动开始文件下载")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "下载成功"),
            @ApiResponse(code = 404, message = "文件没找到"),
            @ApiResponse(code = 403, message = "身份验证失败")
    })
    public ResponseEntity<FileSystemResource> downloadFile(@RequestParam("path") String path, @RequestParam("jwt") String jwt){
        String identification = JwtUtil.getIdentification(jwt);
        // 令牌失效，返回403
        if(identification == null){
            logger.error("jwt error when trying downloading file");
            return new ResponseEntity<FileSystemResource>(null, new HttpHeaders(), 403);
        }
        identification = "u"+identification;
        identification = identification.toLowerCase();
        String file_dir = "/mnt/students/" + identification + path; // 文件的路径
        File target = new File(file_dir);
        // 如果文件找不到，返回404
        if(!target.exists()){
            logger.warn(identification+" download failed due to file "+file_dir+" was not found");
            return new ResponseEntity<FileSystemResource>(null, new HttpHeaders(), HttpStatus.NOT_FOUND);
        }
        if(target.isFile()){
            // 设置下载相关信息
            String[] parts = path.split("/");
            HttpHeaders response_header = new HttpHeaders();
            response_header.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            response_header.setContentDispositionFormData("attachment", URLEncoder.encode(parts[parts.length - 1], StandardCharsets.UTF_8));
            ResponseEntity<FileSystemResource> response = new ResponseEntity<>(new FileSystemResource(file_dir), response_header, HttpStatus.OK);
            logger.info(identification+"successfully downloaded file "+file_dir);
            return response;
        }else{
            ResponseEntity<FileSystemResource> response = new ResponseEntity<FileSystemResource>(HttpStatus.OK);
            try{
               String zipName = FileUtil.packFolder(Paths.get(target.getAbsolutePath()));
               HttpHeaders response_header = new HttpHeaders();
               response_header.setContentType(MediaType.APPLICATION_OCTET_STREAM);
               String[] parts = zipName.split("/");
               response_header.setContentDispositionFormData("attachment", URLEncoder.encode(parts[parts.length - 1], StandardCharsets.UTF_8));
               response = new ResponseEntity<FileSystemResource>(new FileSystemResource(zipName), response_header, HttpStatus.OK);
               logger.info(identification+" successfully zipped and downloaded directory "+zipName);
            }catch(Exception e){
                response = new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
                logger.error("Error occurred when "+identification+" tries to download "+file_dir+" which is "+e.toString());
            }
            return response;
        }
    }

    /**
     * 通过这个接口，向网盘传输文件
     * 接收到文件之后要先检查有没有目标文件夹，然后检查有没有同名的目标文件，最后输出到目标目录，最终修改其拥有者
     * 请注意，除非path是根目录，否则结尾不包含/
     * @param path 要上传文件的地方
     * @param files 要上传的文件，是一个列表
     * @return 一个Response对象，1001意味着上传成功，500意味着上传失败，501意味着有重名文件 / 目录，502意味着文件夹不存在
     * */
    @PostMapping("/upload")
    @ApiOperation(value = "上传文件的接口", notes = "通过这个接口，向网盘特定位置传输文件，不能和文件夹下的其他文件同名")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "文件上传成功"),
            @ApiResponse(code = 501, message = "上传路径不存在"),
            @ApiResponse(code = 500, message = "其他错误")
    })
    public Response uploadFile(@RequestParam("filename")String filename, @RequestParam("path") String path, @RequestParam("file")MultipartFile[] files){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String base_dir = "/mnt/students/" + identification + path;
        Path dir = Paths.get(base_dir);
        Response res = new Response(null);
        // 检查路径有没有问题
        if(!Files.exists(dir) || !Files.isDirectory(dir)){
            res.setMessage("路径不存在");
            res.setCode(501);
            logger.warn(identification+" tries to upload to "+dir+" which doesn't exists");
            return res;
        }
        for(MultipartFile f: files){
            String f_name = FileUtil.getFileName(dir.resolve(f.getOriginalFilename()).toString());// 新的文件的名字
            Path f_path = dir.resolve(f_name);// 新文件的path
            try{
                f.transferTo(f_path);
                // 设置文件的所有者
                Files.setOwner(f_path, FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName(identification));
                // 设置文件的权限
                Files.setPosixFilePermissions(f_path, PosixFilePermissions.fromString("rwx------"));
                logger.info(identification+" successfully uploaded file to "+f_path.toString());
            }catch(IOException e){
                res.setCode(500);
                res.setMessage("IO过程出现问题");
                logger.error("IOException occurred when transferring "+ f.getOriginalFilename()+" to "+f_path.toString());
                return res;
            }
        }
        res.setCode(1001);
        res.setMessage("上传成功");
        return res;
    }

    /**
     * 根据一个分享的id，下载相关文件
     * */
    @GetMapping("/download/share")
    @ApiOperation(value = "根据分享链接下载的接口", notes = "通过分享的id，下载相关文件")
    @ApiResponses({
            @ApiResponse(code = 404, message = "下载链接失效"),
            @ApiResponse(code = 500, message = "服务器出错"),
            @ApiResponse(code = 200, message = "下载成功")
    })
    public ResponseEntity<FileSystemResource> downloadShared(@RequestParam("id")long id){
        HttpHeaders header = new HttpHeaders();
        header.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        ShareLink link = link_repo.getLink(id);
        if(link == null){// 链接无效
            logger.warn("expired/invalid link: "+link.getId());
            return new ResponseEntity<>(null, header, HttpStatus.NOT_FOUND);
        }
        String identification = link.getIdentification();
        String file_dir = "/mnt/students/"+identification+link.getPath();// 目标文件路径
        String[] parts = link.getPath().split("/");
        File f = new File(file_dir);
        if(!f.exists()){// 文件没找到
            logger.warn("shared file not found "+f.getAbsolutePath());
            return new ResponseEntity<>(null, header, HttpStatus.NOT_FOUND);
        }
        try{
            if(f.isDirectory()){// 被分享的是一个文件夹
                String zipFile = FileUtil.packFolder(Paths.get(file_dir));
                String[] zip_parts = zipFile.split("/");
                header.setContentDispositionFormData("attachment", URLEncoder.encode(zip_parts[parts.length-1], StandardCharsets.UTF_8));
                logger.info("successfully downloaded directory "+f.getAbsolutePath()+" through share link "+link.getId());
                return new ResponseEntity<FileSystemResource>(new FileSystemResource(zipFile), header, HttpStatus.OK);
            }else{// 被分享的是一个普通文件
                header.setContentDispositionFormData("attachment", URLEncoder.encode(parts[parts.length - 1], StandardCharsets.UTF_8));
                logger.info("successfully downloaded file "+f.getAbsolutePath()+" through share link "+link.getId());
                return new ResponseEntity<FileSystemResource>(new FileSystemResource(file_dir), header, HttpStatus.OK);
            }
        }catch(Exception e){
            e.printStackTrace();
            if(f.isDirectory()){
                logger.error("failed to download directory "+f.getAbsolutePath()+" through sharing because "+e.toString());
            }else{
                logger.error("failed to download file "+f.getAbsolutePath()+" through sharing because "+e.toString());
            }
            return new ResponseEntity<>(null, header, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PostMapping("/uploadStatus")
    @ApiOperation(value = "查询上传进度", notes = "查询一个用户的全部上传进度，上传成功之后，记录即消除")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "查询成功")
    })
    public Response getStatus(){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        logger.info(identification+"enters uploadStatus");
        UserUploadTasks tasks = util.getUploadStatus(identification);
        if(tasks != null){
            List<UploadTask> ls = tasks.toList();
            logger.info("tasks prepared: "+ls.toString());
            LinkedList<TreeMap<String, Object>> result = new LinkedList<>();
            for(UploadTask t:ls){
                TreeMap<String, Object> item = new TreeMap<>();
                item.put("name", t.getTaskName());
                item.put("finished", t.getFinished());
                item.put("total", t.getTotalSize());
                item.put("velocity", t.getVelocity());
                result.addLast(item);
            }
            Response res = new Response(result);
            res.setCode(1001);
            res.setMessage("查询成功");
            return res;
        }else{
            Response res = new Response(new LinkedList<>());
            res.setCode(1001);
            res.setMessage("查询成功");
            return res;
        }
    }
}